// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner.h"
#include "base/time/time.h"
#include "content/public/test/async_file_test_helper.h"
#include "content/public/test/test_file_system_context.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/disk_cache/disk_cache.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_reader.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/fileapi/file_stream_reader.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_file_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using base::FilePath;
using content::AsyncFileTestHelper;
using net::DrainableIOBuffer;
using net::IOBuffer;
using FileCreationInfo = storage::BlobMemoryController::FileCreationInfo;

namespace storage {
namespace {

    const int kTestDiskCacheStreamIndex = 0;
    const int kTestDiskCacheSideStreamIndex = 1;

    void SaveBlobStatusAndFiles(BlobStatus* status_ptr,
        std::vector<FileCreationInfo>* files_ptr,
        BlobStatus status,
        std::vector<FileCreationInfo> files)
    {
        *status_ptr = status;
        for (FileCreationInfo& info : files) {
            files_ptr->push_back(std::move(info));
        }
    }

    // Our disk cache tests don't need a real data handle since the tests themselves
    // scope the disk cache and entries.
    class EmptyDataHandle : public storage::BlobDataBuilder::DataHandle {
    private:
        ~EmptyDataHandle() override { }
    };

    // A disk_cache::Entry that arbitrarily delays the completion of a read
    // operation to allow testing some races without flake. This is particularly
    // relevant in this unit test, which uses the always-synchronous MEMORY_CACHE.
    class DelayedReadEntry : public disk_cache::Entry {
    public:
        explicit DelayedReadEntry(disk_cache::ScopedEntryPtr entry)
            : entry_(std::move(entry))
        {
        }
        ~DelayedReadEntry() override { EXPECT_FALSE(HasPendingReadCallbacks()); }

        bool HasPendingReadCallbacks() { return !pending_read_callbacks_.empty(); }

        void RunPendingReadCallbacks()
        {
            std::vector<base::Callback<void(void)>> callbacks;
            pending_read_callbacks_.swap(callbacks);
            for (const auto& callback : callbacks)
                callback.Run();
        }

        // From disk_cache::Entry:
        void Doom() override { entry_->Doom(); }

        void Close() override { delete this; } // Note this is required by the API.

        std::string GetKey() const override { return entry_->GetKey(); }

        base::Time GetLastUsed() const override { return entry_->GetLastUsed(); }

        base::Time GetLastModified() const override
        {
            return entry_->GetLastModified();
        }

        int32_t GetDataSize(int index) const override
        {
            return entry_->GetDataSize(index);
        }

        int ReadData(int index,
            int offset,
            IOBuffer* buf,
            int buf_len,
            const CompletionCallback& original_callback) override
        {
            net::TestCompletionCallback callback;
            int rv = entry_->ReadData(index, offset, buf, buf_len, callback.callback());
            DCHECK_NE(rv, net::ERR_IO_PENDING)
                << "Test expects to use a MEMORY_CACHE instance, which is synchronous.";
            pending_read_callbacks_.push_back(base::Bind(original_callback, rv));
            return net::ERR_IO_PENDING;
        }

        int WriteData(int index,
            int offset,
            IOBuffer* buf,
            int buf_len,
            const CompletionCallback& callback,
            bool truncate) override
        {
            return entry_->WriteData(index, offset, buf, buf_len, callback, truncate);
        }

        int ReadSparseData(int64_t offset,
            IOBuffer* buf,
            int buf_len,
            const CompletionCallback& callback) override
        {
            return entry_->ReadSparseData(offset, buf, buf_len, callback);
        }

        int WriteSparseData(int64_t offset,
            IOBuffer* buf,
            int buf_len,
            const CompletionCallback& callback) override
        {
            return entry_->WriteSparseData(offset, buf, buf_len, callback);
        }

        int GetAvailableRange(int64_t offset,
            int len,
            int64_t* start,
            const CompletionCallback& callback) override
        {
            return entry_->GetAvailableRange(offset, len, start, callback);
        }

        bool CouldBeSparse() const override { return entry_->CouldBeSparse(); }

        void CancelSparseIO() override { entry_->CancelSparseIO(); }

        int ReadyForSparseIO(const CompletionCallback& callback) override
        {
            return entry_->ReadyForSparseIO(callback);
        }

    private:
        disk_cache::ScopedEntryPtr entry_;
        std::vector<base::Callback<void(void)>> pending_read_callbacks_;
    };

    std::unique_ptr<disk_cache::Backend> CreateInMemoryDiskCache(
        const scoped_refptr<base::SingleThreadTaskRunner>& thread)
    {
        std::unique_ptr<disk_cache::Backend> cache;
        net::TestCompletionCallback callback;
        int rv = disk_cache::CreateCacheBackend(
            net::MEMORY_CACHE, net::CACHE_BACKEND_DEFAULT, FilePath(), 0, false,
            thread, nullptr, &cache, callback.callback());
        EXPECT_EQ(net::OK, callback.GetResult(rv));

        return cache;
    }

    disk_cache::ScopedEntryPtr CreateDiskCacheEntry(disk_cache::Backend* cache,
        const char* key,
        const std::string& data)
    {
        disk_cache::Entry* temp_entry = nullptr;
        net::TestCompletionCallback callback;
        int rv = cache->CreateEntry(key, &temp_entry, callback.callback());
        if (callback.GetResult(rv) != net::OK)
            return nullptr;
        disk_cache::ScopedEntryPtr entry(temp_entry);

        scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(data);
        rv = entry->WriteData(kTestDiskCacheStreamIndex, 0, iobuffer.get(),
            iobuffer->size(), callback.callback(), false);
        EXPECT_EQ(static_cast<int>(data.size()), callback.GetResult(rv));
        return entry;
    }

    disk_cache::ScopedEntryPtr CreateDiskCacheEntryWithSideData(
        disk_cache::Backend* cache,
        const char* key,
        const std::string& data,
        const std::string& side_data)
    {
        disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntry(cache, key, data);
        scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(side_data);
        net::TestCompletionCallback callback;
        int rv = entry->WriteData(kTestDiskCacheSideStreamIndex, 0, iobuffer.get(),
            iobuffer->size(), callback.callback(), false);
        EXPECT_EQ(static_cast<int>(side_data.size()), callback.GetResult(rv));
        return entry;
    }

    template <typename T>
    void SetValue(T* address, T value)
    {
        *address = value;
    }

    class FakeFileStreamReader : public FileStreamReader {
    public:
        explicit FakeFileStreamReader(const std::string& contents)
            : buffer_(new DrainableIOBuffer(
                new net::StringIOBuffer(
                    std::unique_ptr<std::string>(new std::string(contents))),
                contents.size()))
            , net_error_(net::OK)
            , size_(contents.size())
        {
        }
        FakeFileStreamReader(const std::string& contents, uint64_t size)
            : buffer_(new DrainableIOBuffer(
                new net::StringIOBuffer(
                    std::unique_ptr<std::string>(new std::string(contents))),
                contents.size()))
            , net_error_(net::OK)
            , size_(size)
        {
        }

        ~FakeFileStreamReader() override { }

        void SetReturnError(int net_error) { net_error_ = net_error; }

        void SetAsyncRunner(base::SingleThreadTaskRunner* runner)
        {
            async_task_runner_ = runner;
        }

        int Read(net::IOBuffer* buf,
            int buf_length,
            const net::CompletionCallback& done) override
        {
            DCHECK(buf);
            // When async_task_runner_ is not set, return synchronously.
            if (!async_task_runner_.get()) {
                if (net_error_ == net::OK) {
                    return ReadImpl(buf, buf_length, net::CompletionCallback());
                } else {
                    return net_error_;
                }
            }

            // Otherwise always return asynchronously.
            if (net_error_ == net::OK) {
                async_task_runner_->PostTask(
                    FROM_HERE,
                    base::Bind(base::IgnoreResult(&FakeFileStreamReader::ReadImpl),
                        base::Unretained(this), make_scoped_refptr(buf),
                        buf_length, done));
            } else {
                async_task_runner_->PostTask(FROM_HERE, base::Bind(done, net_error_));
            }
            return net::ERR_IO_PENDING;
        }

        int64_t GetLength(
            const net::Int64CompletionCallback& size_callback) override
        {
            // When async_task_runner_ is not set, return synchronously.
            if (!async_task_runner_.get()) {
                if (net_error_ == net::OK) {
                    return size_;
                } else {
                    return net_error_;
                }
            }
            if (net_error_ == net::OK) {
                async_task_runner_->PostTask(FROM_HERE, base::Bind(size_callback, size_));
            } else {
                async_task_runner_->PostTask(
                    FROM_HERE,
                    base::Bind(size_callback, static_cast<int64_t>(net_error_)));
            }
            return net::ERR_IO_PENDING;
        }

    private:
        int ReadImpl(scoped_refptr<net::IOBuffer> buf,
            int buf_length,
            const net::CompletionCallback& done)
        {
            CHECK_GE(buf_length, 0);
            int length = std::min(buf_length, buffer_->BytesRemaining());
            memcpy(buf->data(), buffer_->data(), length);
            buffer_->DidConsume(length);
            if (done.is_null()) {
                return length;
            }
            done.Run(length);
            return net::ERR_IO_PENDING;
        }

        scoped_refptr<net::DrainableIOBuffer> buffer_;
        scoped_refptr<base::SingleThreadTaskRunner> async_task_runner_;
        int net_error_;
        uint64_t size_;

        DISALLOW_COPY_AND_ASSIGN(FakeFileStreamReader);
    };

    class MockFileStreamReaderProvider
        : public BlobReader::FileStreamReaderProvider {
    public:
        ~MockFileStreamReaderProvider() override { }

        MOCK_METHOD4(CreateForLocalFileMock,
            FileStreamReader*(base::TaskRunner* task_runner,
                const FilePath& file_path,
                int64_t initial_offset,
                const base::Time& expected_modification_time));
        MOCK_METHOD4(CreateFileStreamReaderMock,
            FileStreamReader*(const GURL& filesystem_url,
                int64_t offset,
                int64_t max_bytes_to_read,
                const base::Time& expected_modification_time));
        // Since we're returning a move-only type, we have to do some delegation for
        // gmock.
        std::unique_ptr<FileStreamReader> CreateForLocalFile(
            base::TaskRunner* task_runner,
            const base::FilePath& file_path,
            int64_t initial_offset,
            const base::Time& expected_modification_time) override
        {
            return base::WrapUnique(CreateForLocalFileMock(
                task_runner, file_path, initial_offset, expected_modification_time));
        }

        std::unique_ptr<FileStreamReader> CreateFileStreamReader(
            const GURL& filesystem_url,
            int64_t offset,
            int64_t max_bytes_to_read,
            const base::Time& expected_modification_time) override
        {
            return base::WrapUnique(CreateFileStreamReaderMock(
                filesystem_url, offset, max_bytes_to_read, expected_modification_time));
        }
    };

} // namespace

class BlobReaderTest : public ::testing::Test {
public:
    BlobReaderTest() { }
    ~BlobReaderTest() override { }

    void TearDown() override
    {
        reader_.reset();
        blob_handle_.reset();
        base::RunLoop().RunUntilIdle();
        base::RunLoop().RunUntilIdle();
    }

protected:
    void InitializeReader(BlobDataBuilder* builder)
    {
        blob_handle_ = builder ? context_.AddFinishedBlob(builder) : nullptr;
        provider_ = new MockFileStreamReaderProvider();
        reader_.reset(new BlobReader(blob_handle_.get(),
            base::WrapUnique(provider_),
            message_loop_.task_runner().get()));
    }

    // Takes ownership of the file reader (the blob reader takes ownership).
    void ExpectLocalFileCall(const FilePath& file_path,
        base::Time modification_time,
        uint64_t initial_offset,
        FakeFileStreamReader* reader)
    {
        EXPECT_CALL(*provider_, CreateForLocalFileMock(message_loop_.task_runner().get(), file_path, initial_offset, modification_time))
            .WillOnce(testing::Return(reader));
    }

    // Takes ownership of the file reader (the blob reader takes ownership).
    void ExpectFileSystemCall(const GURL& filesystem_url,
        int64_t offset,
        int64_t max_bytes_to_read,
        base::Time expected_modification_time,
        FakeFileStreamReader* reader)
    {
        EXPECT_CALL(*provider_, CreateFileStreamReaderMock(filesystem_url, offset, max_bytes_to_read, expected_modification_time))
            .WillOnce(testing::Return(reader));
    }

    void CheckSizeCalculatedSynchronously(size_t expected_size, int async_size)
    {
        EXPECT_EQ(-1, async_size);
        EXPECT_EQ(net::OK, reader_->net_error());
        EXPECT_EQ(expected_size, reader_->total_size());
        EXPECT_TRUE(reader_->total_size_calculated());
    }

    void CheckSizeNotCalculatedYet(int async_size)
    {
        EXPECT_EQ(-1, async_size);
        EXPECT_EQ(net::OK, reader_->net_error());
        EXPECT_FALSE(reader_->total_size_calculated());
    }

    void CheckSizeCalculatedAsynchronously(size_t expected_size,
        int async_result)
    {
        EXPECT_EQ(net::OK, async_result);
        EXPECT_EQ(net::OK, reader_->net_error());
        EXPECT_EQ(expected_size, reader_->total_size());
        EXPECT_TRUE(reader_->total_size_calculated());
    }

    scoped_refptr<net::IOBuffer> CreateBuffer(uint64_t size)
    {
        return scoped_refptr<net::IOBuffer>(
            new net::IOBuffer(static_cast<size_t>(size)));
    }

    bool IsReaderTotalSizeCalculated()
    {
        return reader_->total_size_calculated();
    }

    BlobStorageContext context_;
    std::unique_ptr<BlobDataHandle> blob_handle_;
    MockFileStreamReaderProvider* provider_ = nullptr;
    base::MessageLoop message_loop_;
    std::unique_ptr<BlobReader> reader_;

private:
    DISALLOW_COPY_AND_ASSIGN(BlobReaderTest);
};

TEST_F(BlobReaderTest, BasicMemory)
{
    BlobDataBuilder b("uuid");
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    b.AppendData(kData);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_TRUE(reader_->IsInMemory());
    CheckSizeCalculatedSynchronously(kDataSize, size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kDataSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize));
}

TEST_F(BlobReaderTest, BasicFile)
{
    BlobDataBuilder b("uuid");
    const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();
    b.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&b);

    // Non-async reader.
    ExpectLocalFileCall(kPath, kTime, 0, new FakeFileStreamReader(kData));

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_FALSE(reader_->IsInMemory());
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}

TEST_F(BlobReaderTest, BasicFileSystem)
{
    BlobDataBuilder b("uuid");
    const GURL kURL("file://test_file/here.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();
    b.AppendFileSystemFile(kURL, 0, kData.size(), kTime);
    this->InitializeReader(&b);
    // Non-async reader.
    ExpectFileSystemCall(kURL, 0, kData.size(), kTime,
        new FakeFileStreamReader(kData));

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_FALSE(reader_->IsInMemory());

    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}

TEST_F(BlobReaderTest, BasicDiskCache)
{
    std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(message_loop_.task_runner());
    ASSERT_TRUE(cache);

    BlobDataBuilder b("uuid");
    const std::string kData = "Test Blob Data";
    scoped_refptr<BlobDataBuilder::DataHandle> data_handle = new EmptyDataHandle();
    disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntry(cache.get(), "test entry", kData);
    b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    EXPECT_FALSE(reader_->has_side_data());

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size()));
}

TEST_F(BlobReaderTest, DiskCacheWithSideData)
{
    std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(message_loop_.task_runner());
    ASSERT_TRUE(cache);

    BlobDataBuilder b("uuid");
    const std::string kData = "Test Blob Data";
    const std::string kSideData = "Test side data";
    scoped_refptr<BlobDataBuilder::DataHandle> data_handle = new EmptyDataHandle();
    disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntryWithSideData(
        cache.get(), "test entry", kData, kSideData);
    b.AppendDiskCacheEntryWithSideData(data_handle, entry.get(),
        kTestDiskCacheStreamIndex,
        kTestDiskCacheSideStreamIndex);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    EXPECT_TRUE(reader_->has_side_data());
    BlobReader::Status status = BlobReader::Status::DONE;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->ReadSideData(
            base::Bind(&SetValue<BlobReader::Status>, &status)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_TRUE(reader_->side_data());
    std::string result(reader_->side_data()->data(),
        reader_->side_data()->size());
    EXPECT_EQ(kSideData, result);
}

TEST_F(BlobReaderTest, BufferLargerThanMemory)
{
    BlobDataBuilder b("uuid");
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    const size_t kBufferSize = 10ul;
    b.AppendData(kData);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kBufferSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize));
}

TEST_F(BlobReaderTest, MemoryRange)
{
    BlobDataBuilder b("uuid");
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    const size_t kSeekOffset = 2ul;
    const uint64_t kReadLength = 4ull;
    b.AppendData(kData);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);

    reader_->SetReadRange(kSeekOffset, kReadLength);
    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kDataSize - kSeekOffset, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "llo!", kReadLength));
}

TEST_F(BlobReaderTest, BufferSmallerThanMemory)
{
    BlobDataBuilder b("uuid");
    const std::string kData("Hello!!!");
    const size_t kBufferSize = 4ul;
    b.AppendData(kData);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kBufferSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "Hell", kBufferSize));

    bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kBufferSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "o!!!", kBufferSize));
}

TEST_F(BlobReaderTest, SegmentedBufferAndMemory)
{
    BlobDataBuilder b("uuid");
    const size_t kNumItems = 10;
    const size_t kItemSize = 6;
    const size_t kBufferSize = 10;
    const size_t kTotalSize = kNumItems * kItemSize;
    char current_value = 0;
    for (size_t i = 0; i < kNumItems; i++) {
        char buf[kItemSize];
        for (size_t j = 0; j < kItemSize; j++) {
            buf[j] = current_value++;
        }
        b.AppendData(buf, kItemSize);
    }
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kTotalSize, size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));

    current_value = 0;
    for (size_t i = 0; i < kTotalSize / kBufferSize; i++) {
        int bytes_read = 0;
        int async_bytes_read = 0;
        EXPECT_EQ(BlobReader::Status::DONE,
            reader_->Read(buffer.get(), kBufferSize, &bytes_read,
                base::Bind(&SetValue<int>, &async_bytes_read)));
        EXPECT_EQ(net::OK, reader_->net_error());
        EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read));
        EXPECT_EQ(0, async_bytes_read);
        for (size_t j = 0; j < kBufferSize; j++) {
            EXPECT_EQ(current_value, buffer->data()[j]);
            current_value++;
        }
    }
}

TEST_F(BlobReaderTest, FileAsync)
{
    BlobDataBuilder b("uuid");
    const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();
    b.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&b);

    std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
    reader->SetAsyncRunner(message_loop_.task_runner().get());

    ExpectLocalFileCall(kPath, kTime, 0, reader.release());

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_FALSE(reader_->IsInMemory());
    CheckSizeNotCalculatedYet(size_result);
    base::RunLoop().RunUntilIdle();
    CheckSizeCalculatedAsynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
    EXPECT_EQ(0, bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}

TEST_F(BlobReaderTest, FileSystemAsync)
{
    BlobDataBuilder b("uuid");
    const GURL kURL("file://test_file/here.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();
    b.AppendFileSystemFile(kURL, 0, kData.size(), kTime);
    this->InitializeReader(&b);

    std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
    reader->SetAsyncRunner(message_loop_.task_runner().get());

    ExpectFileSystemCall(kURL, 0, kData.size(), kTime, reader.release());

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeNotCalculatedYet(size_result);
    EXPECT_FALSE(reader_->IsInMemory());
    base::RunLoop().RunUntilIdle();
    CheckSizeCalculatedAsynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
    EXPECT_EQ(0, bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size()));
}

TEST_F(BlobReaderTest, DiskCacheAsync)
{
    std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(message_loop_.task_runner());
    ASSERT_TRUE(cache);

    BlobDataBuilder b("uuid");
    const std::string kData = "Test Blob Data";
    scoped_refptr<BlobDataBuilder::DataHandle> data_handle = new EmptyDataHandle();
    std::unique_ptr<DelayedReadEntry> delayed_read_entry(new DelayedReadEntry(
        CreateDiskCacheEntry(cache.get(), "test entry", kData)));
    b.AppendDiskCacheEntry(data_handle, delayed_read_entry.get(),
        kTestDiskCacheStreamIndex);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeCalculatedSynchronously(kData.size(), size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_TRUE(delayed_read_entry->HasPendingReadCallbacks());
    delayed_read_entry->RunPendingReadCallbacks();
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(0, bytes_read);
    EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read));
    EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size()));
}

TEST_F(BlobReaderTest, FileRange)
{
    BlobDataBuilder b("uuid");
    const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
    // We check the offset in the ExpectLocalFileCall mock.
    const std::string kRangeData = "leD";
    const std::string kData = "FileData!!!";
    const uint64_t kOffset = 2;
    const uint64_t kReadLength = 3;
    const base::Time kTime = base::Time::Now();
    b.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&b);

    std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData));
    reader->SetAsyncRunner(message_loop_.task_runner().get());
    ExpectLocalFileCall(kPath, kTime, 0, reader.release());

    // We create the reader again with the offset after the seek.
    reader.reset(new FakeFileStreamReader(kRangeData));
    reader->SetAsyncRunner(message_loop_.task_runner().get());
    ExpectLocalFileCall(kPath, kTime, kOffset, reader.release());

    int size_result = -1;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    base::RunLoop().RunUntilIdle();

    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->SetReadRange(kOffset, kReadLength));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kReadLength, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kReadLength, static_cast<size_t>(async_bytes_read));
    EXPECT_EQ(0, bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "leD", kReadLength));
}

TEST_F(BlobReaderTest, DiskCacheRange)
{
    std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(message_loop_.task_runner());
    ASSERT_TRUE(cache);

    BlobDataBuilder b("uuid");
    const std::string kData = "Test Blob Data";
    const uint64_t kOffset = 2;
    const uint64_t kReadLength = 3;
    scoped_refptr<BlobDataBuilder::DataHandle> data_handle = new EmptyDataHandle();
    disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntry(cache.get(), "test entry", kData);
    b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex);
    this->InitializeReader(&b);

    int size_result = -1;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));

    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength);
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->SetReadRange(kOffset, kReadLength));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kReadLength, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(0, memcmp(buffer->data(), "st ", kReadLength));
}

TEST_F(BlobReaderTest, FileSomeAsyncSegmentedOffsetsUnknownSizes)
{
    // This tests includes:
    // * Unknown file sizes (item length of uint64_t::max) for every other item.
    // * Offsets for every 3rd file item.
    // * Non-async reader for every 4th file item.
    BlobDataBuilder b("uuid");
    const FilePath kPathBase = FilePath::FromUTF8Unsafe("/fake/file.txt");
    const base::Time kTime = base::Time::Now();
    const size_t kNumItems = 10;
    const size_t kItemSize = 6;
    const size_t kBufferSize = 10;
    const size_t kTotalSize = kNumItems * kItemSize;
    char current_value = 0;
    // Create blob and reader.
    for (size_t i = 0; i < kNumItems; i++) {
        current_value += kItemSize;
        FilePath path = kPathBase.Append(
            FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value)));
        uint64_t offset = i % 3 == 0 ? 1 : 0;
        uint64_t size = kItemSize;
        b.AppendFile(path, offset, size, kTime);
    }
    this->InitializeReader(&b);

    // Set expectations.
    current_value = 0;
    for (size_t i = 0; i < kNumItems; i++) {
        uint64_t offset = i % 3 == 0 ? 1 : 0;
        std::unique_ptr<char[]> buf(new char[kItemSize + offset]);
        if (offset > 0) {
            memset(buf.get(), 7, offset);
        }
        for (size_t j = 0; j < kItemSize; j++) {
            buf.get()[j + offset] = current_value++;
        }
        std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(
            std::string(buf.get() + offset, kItemSize), kItemSize + offset));
        if (i % 4 != 0) {
            reader->SetAsyncRunner(message_loop_.task_runner().get());
        }
        FilePath path = kPathBase.Append(
            FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value)));
        ExpectLocalFileCall(path, kTime, offset, reader.release());
    }

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeNotCalculatedYet(size_result);
    base::RunLoop().RunUntilIdle();
    CheckSizeCalculatedAsynchronously(kTotalSize, size_result);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));

    current_value = 0;
    for (size_t i = 0; i < kTotalSize / kBufferSize; i++) {
        int bytes_read = 0;
        int async_bytes_read = 0;
        EXPECT_EQ(BlobReader::Status::IO_PENDING,
            reader_->Read(buffer.get(), kBufferSize, &bytes_read,
                base::Bind(&SetValue<int>, &async_bytes_read)));
        base::RunLoop().RunUntilIdle();
        EXPECT_EQ(net::OK, reader_->net_error());
        EXPECT_EQ(0, bytes_read);
        EXPECT_EQ(kBufferSize, static_cast<size_t>(async_bytes_read));
        for (size_t j = 0; j < kBufferSize; j++) {
            EXPECT_EQ(current_value, buffer->data()[j]);
            current_value++;
        }
    }
}

TEST_F(BlobReaderTest, MixedContent)
{
    // Includes data, a file, and a disk cache entry.
    std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(message_loop_.task_runner());
    ASSERT_TRUE(cache);

    BlobDataBuilder b("uuid");
    const std::string kData1("Hello ");
    const std::string kData2("there. ");
    const std::string kData3("This ");
    const std::string kData4("is multi-content.");
    const uint64_t kDataSize = 35;

    const base::Time kTime = base::Time::Now();
    const FilePath kData1Path = FilePath::FromUTF8Unsafe("/fake/file.txt");

    disk_cache::ScopedEntryPtr entry3 = CreateDiskCacheEntry(cache.get(), "test entry", kData3);

    b.AppendFile(kData1Path, 0, kData1.size(), kTime);
    b.AppendData(kData2);
    b.AppendDiskCacheEntry(
        scoped_refptr<BlobDataBuilder::DataHandle>(new EmptyDataHandle()),
        entry3.get(), kTestDiskCacheStreamIndex);
    b.AppendData(kData4);

    this->InitializeReader(&b);

    std::unique_ptr<FakeFileStreamReader> reader(
        new FakeFileStreamReader(kData1));
    reader->SetAsyncRunner(message_loop_.task_runner().get());
    ExpectLocalFileCall(kData1Path, kTime, 0, reader.release());

    int size_result = -1;
    EXPECT_FALSE(IsReaderTotalSizeCalculated());
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    CheckSizeNotCalculatedYet(size_result);
    base::RunLoop().RunUntilIdle();
    CheckSizeCalculatedAsynchronously(kDataSize, size_result);

    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize);

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kDataSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(0, async_bytes_read);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(0, bytes_read);
    EXPECT_EQ(kDataSize, static_cast<size_t>(async_bytes_read));
    EXPECT_EQ(0, memcmp(buffer->data(), "Hello there. This is multi-content.", kDataSize));
}

TEST_F(BlobReaderTest, StateErrors)
{
    // Test common variables
    int bytes_read = -1;
    int async_bytes_read = -1;
    int size_result = -1;
    const std::string kData("Hello!!!");

    // Case: Blob handle is a nullptr.
    InitializeReader(nullptr);
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
    EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(10);
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->Read(buffer.get(), 10, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());

    // Case: Not calling CalculateSize before SetReadRange.
    BlobDataBuilder builder1("uuid1");
    builder1.AppendData(kData);
    InitializeReader(&builder1);
    EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10));
    EXPECT_EQ(net::ERR_FAILED, reader_->net_error());
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->Read(buffer.get(), 10, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));

    // Case: Not calling CalculateSize before Read.
    BlobDataBuilder builder2("uuid2");
    builder2.AppendData(kData);
    InitializeReader(&builder2);
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->Read(buffer.get(), 10, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
}

TEST_F(BlobReaderTest, FileErrorsSync)
{
    int size_result = -1;
    const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();

    // Case: Error on length query.
    BlobDataBuilder builder1("uuid1");
    builder1.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&builder1);
    FakeFileStreamReader* reader = new FakeFileStreamReader(kData);
    reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
    ExpectLocalFileCall(kPath, kTime, 0, reader);

    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());

    // Case: Error on read.
    BlobDataBuilder builder2("uuid2");
    builder2.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&builder2);
    reader = new FakeFileStreamReader(kData);
    ExpectLocalFileCall(kPath, kTime, 0, reader);
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    reader->SetReturnError(net::ERR_FILE_NOT_FOUND);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}

TEST_F(BlobReaderTest, FileErrorsAsync)
{
    int size_result = -1;
    const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt");
    const std::string kData = "FileData!!!";
    const base::Time kTime = base::Time::Now();

    // Case: Error on length query.
    BlobDataBuilder builder1("uuid1");
    builder1.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&builder1);
    FakeFileStreamReader* reader = new FakeFileStreamReader(kData);
    reader->SetAsyncRunner(message_loop_.task_runner().get());
    reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
    ExpectLocalFileCall(kPath, kTime, 0, reader);

    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_EQ(net::OK, reader_->net_error());
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, size_result);
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());

    // Case: Error on read.
    BlobDataBuilder builder2("uuid2");
    builder2.AppendFile(kPath, 0, kData.size(), kTime);
    this->InitializeReader(&builder2);
    reader = new FakeFileStreamReader(kData);
    ExpectLocalFileCall(kPath, kTime, 0, reader);
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    reader->SetReturnError(net::ERR_FILE_NOT_FOUND);
    reader->SetAsyncRunner(message_loop_.task_runner().get());

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size()));
    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->Read(buffer.get(), kData.size(), &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, async_bytes_read);
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}

TEST_F(BlobReaderTest, RangeError)
{
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    const uint64_t kReadLength = 4ull;

    // Case: offset too high.
    BlobDataBuilder b("uuid1");
    b.AppendData(kData);
    this->InitializeReader(&b);
    int size_result = -1;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize);
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->SetReadRange(kDataSize + 1, kReadLength));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());

    // Case: length too long.
    BlobDataBuilder b2("uuid2");
    b2.AppendData(kData);
    this->InitializeReader(&b2);
    size_result = -1;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    buffer = CreateBuffer(kDataSize + 1);
    EXPECT_EQ(BlobReader::Status::NET_ERROR,
        reader_->SetReadRange(0, kDataSize + 1));
    EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error());
}

TEST_F(BlobReaderTest, HandleBeforeAsyncCancel)
{
    const std::string kUuid("uuid1");
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    std::vector<FileCreationInfo> files;

    BlobDataBuilder b(kUuid);
    b.AppendFutureData(kDataSize);
    BlobStatus can_populate_status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
    blob_handle_ = context_.BuildBlob(
        b, base::Bind(&SaveBlobStatusAndFiles, &can_populate_status, &files));
    EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status);
    provider_ = new MockFileStreamReaderProvider();
    reader_.reset(new BlobReader(blob_handle_.get(), base::WrapUnique(provider_),
        message_loop_.task_runner().get()));
    int size_result = -1;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_FALSE(reader_->IsInMemory());
    context_.CancelBuildingBlob(kUuid,
        BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(net::ERR_FAILED, size_result);
}

TEST_F(BlobReaderTest, ReadFromIncompleteBlob)
{
    const std::string kUuid("uuid1");
    const std::string kData("Hello!!!");
    const size_t kDataSize = 8ul;
    std::vector<FileCreationInfo> files;

    BlobDataBuilder b(kUuid);
    b.AppendFutureData(kDataSize);
    BlobStatus can_populate_status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
    blob_handle_ = context_.BuildBlob(
        b, base::Bind(&SaveBlobStatusAndFiles, &can_populate_status, &files));
    EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status);
    provider_ = new MockFileStreamReaderProvider();
    reader_.reset(new BlobReader(blob_handle_.get(), base::WrapUnique(provider_),
        message_loop_.task_runner().get()));
    int size_result = -1;
    EXPECT_EQ(BlobReader::Status::IO_PENDING,
        reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result)));
    EXPECT_FALSE(reader_->IsInMemory());
    b.PopulateFutureData(0, kData.data(), 0, kDataSize);
    context_.NotifyTransportComplete(kUuid);
    base::RunLoop().RunUntilIdle();
    CheckSizeCalculatedAsynchronously(kDataSize, size_result);
    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize));

    int bytes_read = 0;
    int async_bytes_read = 0;
    EXPECT_EQ(BlobReader::Status::DONE,
        reader_->Read(buffer.get(), kDataSize, &bytes_read,
            base::Bind(&SetValue<int>, &async_bytes_read)));
    EXPECT_EQ(net::OK, reader_->net_error());
    EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read));
    EXPECT_EQ(0, async_bytes_read);
    EXPECT_EQ(kData, std::string(buffer->data(), kDataSize));
    EXPECT_EQ(net::OK, size_result);
}

} // namespace storage
